#pragma once
#include "ControlBase.h"
#include "Controls.h"
#include "helper.h"
#include <unordered_map>

static std::unordered_map<HWND, class Form *> ProgramForms = std::unordered_map<HWND, class Form *>();
static LRESULT CALLBACK ___MSG_Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
class Form : public Control
{
public:
    virtual UIClass Type() { return UIClass::UI_Form; }
    HMENU Menu = NULL;
    Form *Parent = NULL;
    UINT Closed = WM_NULL;
    DWORD Style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    READONLY_PROPERTY(SIZE, ClientSize);
    GET(SIZE, ClientSize)
    {
        RECT crec = {};
        GetClientRect(this->Handle, &crec);
        return {crec.right - crec.left, crec.bottom - crec.top};
    }
    Form(std::wstring _text = L"NativeWindow", POINT _location = {0, 0}, SIZE _size = {600, 400})
    {
        this->Location = _location;
        this->Size = _size;
        static int classIndex = 0;
        WNDCLASSW wndclass = {0};
        auto wclass = FormatW(L"NativeWindow_%d", classIndex++);
        {
            wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
            wndclass.cbClsExtra = 0;
            wndclass.cbWndExtra = 0;
            wndclass.lpfnWndProc = ___MSG_Proc;
            wndclass.hInstance = GetModuleHandleA(NULL);
            wndclass.hIcon = LoadIconW(NULL, MAKEINTRESOURCEW(32512));
            wndclass.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(32512));
            wndclass.lpszMenuName = NULL;
            wndclass.lpszClassName = wclass;
        }
        if (!RegisterClass(&wndclass))
        {
            return;
        }
        this->Handle = CreateWindowW(wclass,
                                     _text.c_str(),
                                     this->Style,
                                     this->Location.x == 0 ? CW_USEDEFAULT : this->Location.x,
                                     this->Location.y == 0 ? CW_USEDEFAULT : this->Location.y,
                                     this->Size.cx == 0 ? CW_USEDEFAULT : this->Size.cx,
                                     this->Size.cy == 0 ? CW_USEDEFAULT : this->Size.cy,
                                     this->Parent ? this->Parent->Handle : NULL,
                                     this->Menu,
                                     GetModuleHandle(NULL),
                                     NULL);
        Render = new D2DGraphics(this->Handle);
        ProgramForms[this->Handle] = this;
        this->MostParent = this;
        DragAcceptFiles(this->Handle, TRUE);
    }
    ~Form()
    {
        delete Render;
        DestroyWindow(this->Handle);
    }
    void Show()
    {
        if (this->Menu)
            SetMenu(this->Handle, this->Menu);
        SetWindowLong(this->Handle, GWL_STYLE, this->Style);
        ShowWindow(this->Handle, SW_SHOWNORMAL);
    }
    void Hide()
    {
        ShowWindow(this->Handle, SW_HIDE);
    }
    static bool DoEvent()
    {
        MSG msg;
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            DispatchMessage(&msg);
            return true;
        }
        Sleep(1);
        return false;
    }
    static void DoFoceEvent()
    {
        MSG msg;
        if (GetMessage(&msg, NULL, 0, 0))
        {
            DispatchMessage(&msg);
        }
    }
    void Update() override
    {
        this->Render->BeginRender(this->BackColor);
        if (this->Image)
        {
            this->RenderImage();
        }

        for (int i = 0; i < this->Count; i++)
        {
            auto c = this->operator[](i);
            if (!c->Visable)
                continue;
            auto location = c->Location;
            if (c->Render == NULL)
            {
                c->Render = this->Render;
            }
            c->Update();
        }
        for (auto fc : this->ForeGroundControls)
        {
            fc->Update();
        }
        this->Render->EndRender();
    }

    bool ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam, int xof, int yof) override
    {
        POINT mouse;
        GetCursorPos(&mouse);
        ScreenToClient(this->Handle, &mouse);
        Control *HitControl = NULL;
        switch (message)
        {
        case WM_MOUSEMOVE: // mouse move
        {
            if (GetKeyState(VK_LBUTTON) & 0x8000 && this->Selected)
            {
                if (this->Selected->IsVisual)
                {
                    HitControl = this->Selected;
                    auto location = this->Selected->AbsLocation;
                    this->Selected->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                    break;
                }
            }
            auto lastUnderMouse = this->UnderMouse;
            this->UnderMouse = NULL;
            bool is_First = true;
        reExc:
            for (int i = 0; i < this->Count; i++)
            {
                auto c = this->operator[](i);
                if (!c->Visable)
                    continue;
                auto location = c->Location;
                auto size = c->ActualSize();
                if (
                    mouse.x >= location.x &&
                    mouse.y >= location.y &&
                    mouse.x <= (location.x + size.cx) &&
                    mouse.y <= (location.y + size.cy))
                {
                    if (is_First)
                    {
                        if (c->Type() == UIClass::UI_ComboBox)
                        {
                            HitControl = c;
                            this->UnderMouse = c;
                            c->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                            goto ext;
                        }
                    }
                    else
                    {
                        HitControl = c;
                        this->UnderMouse = c;
                        c->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                    }
                }
            }
            if (is_First)
            {
                is_First = false;
                goto reExc;
            }
        ext:
            if (lastUnderMouse != this->UnderMouse)
            {
                if (this->UnderMouse)
                    this->UnderMouse->SingleUpdate();
                // if (lastUnderMouse)
                //     lastUnderMouse->SingleUpdate();
            }
        }
        break;
        case WM_DROPFILES:
        case WM_MOUSEWHEEL:  // mouse wheel
        case WM_LBUTTONDOWN: // mouse down
        case WM_RBUTTONDOWN:
        case WM_MBUTTONDOWN:
        case WM_LBUTTONUP: // mouse up
        case WM_RBUTTONUP:
        case WM_MBUTTONUP:
        case WM_LBUTTONDBLCLK: // mouse double click
        {
            if (WM_LBUTTONUP == message && this->Selected)
            {
                if (this->Selected->IsVisual)
                {
                    HitControl = this->Selected;
                    auto location = this->Selected->AbsLocation;
                    this->Selected->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                    break;
                }
            }
            bool is_First = true;
        reExc1:
            for (int i = 0; i < this->Count; i++)
            {
                auto c = this->operator[](i);
                if (!c->Visable)
                    continue;
                auto location = c->Location;
                auto size = c->ActualSize();
                if (
                    mouse.x >= location.x &&
                    mouse.y >= location.y &&
                    mouse.x <= (location.x + size.cx) &&
                    mouse.y <= (location.y + size.cy))
                {
                    if (is_First)
                    {
                        if (c->Type() == UIClass::UI_ComboBox)
                        {
                            HitControl = c;
                            c->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                            goto ext1;
                        }
                    }
                    else
                    {
                        HitControl = c;
                        c->ProcessMessage(message, wParam, lParam, mouse.x - location.x, mouse.y - location.y);
                    }
                }
            }
            if (is_First)
            {
                is_First = false;
                goto reExc1;
            }
        ext1:;
        }
        break;
        case WM_KEYDOWN: // keyboard down
        {
            if (this->Selected)
            {
                if (this->Selected->ProcessMessage(message, wParam, lParam, xof, yof))
                {
                    if (this->Selected->IsVisual)
                    {
                        HitControl = this->Selected;
                        KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
                        this->OnKeyDown(this, event_obj);
                    }
                }
            }
            else
            {
                KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
                this->OnKeyDown(this, event_obj);
            }
        }
        break;
        case WM_KEYUP: // keyboard up
        {
            if (this->Selected)
            {
                if (this->Selected->ProcessMessage(message, wParam, lParam, xof, yof))
                {
                    HitControl = this->Selected;
                    KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
                    this->OnKeyUp(this, event_obj);
                }
            }
            else
            {
                KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
                this->OnKeyUp(this, event_obj);
            }
        }
        break;
        case WM_SIZE:
        {
            RECT rec;
            GetClientRect(this->Handle, &rec);
            this->Size = SIZE{rec.right - rec.left, rec.bottom - rec.top};
            this->SizeChanged(this);
            this->Render->Resize();
            this->Update();
        }
        break;
        case WM_MOVE:
        {
            RECT client_rectangle;
            GetClientRect(this->Handle, &client_rectangle);
            this->Location = POINT{client_rectangle.left, client_rectangle.top};
            this->Moved(this);
            this->Update();
            if (this->Parent)
                this->Parent->Update();
        }
        break;
        case WM_PAINT:
        {
            this->OnPaint(this);
            this->Update();
        }
        break;
        case WM_CHAR:
        {
            if (this->Selected)
            {
                if (this->Selected->ProcessMessage(message, wParam, lParam, xof, yof))
                {
                    HitControl = this->Selected;
                    this->OnCharInput(this, (wchar_t)(wParam));
                }
            }
            else
            {
                HitControl = this;
                this->OnCharInput(this, (wchar_t)(wParam));
            }
        }
        break;
        case WM_IME_COMPOSITION: // input app action
        {
            if (this->Selected)
            {
                HitControl = this->Selected;
                this->Selected->ProcessMessage(message, wParam, lParam, xof, yof);
            }
        }
        break;
        case WM_CLOSE:
        {
            ProgramForms.erase(this->Handle);
            if (this->Parent)
            {
                this->Parent->RemoveControl(this);
            }
            Closed = WM_CLOSE;
            delete this;
        }
        break;
        };

        if (WM_LBUTTONDOWN == message && HitControl == NULL && this->Selected && HitControl != this->Selected)
        {
            auto se = this->Selected;
            this->Selected = NULL;
            se->SingleUpdate();
        }

        return true;
    }
    Control *AddControl(Control *c)
    {
        if (c->Type() == UIClass::UI_Form)
        {
            ((Form *)c)->Parent = this;
        }
        return Control::AddControl(c);
    }
    bool WaitForAllObjects()
    {
        return ((Closed & WM_CLOSE) == WM_CLOSE);
    }
};
LRESULT CALLBACK ___MSG_Proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    auto window = ProgramForms[hWnd];
    if (window)
    {
        window->ProcessMessage(message, wParam, lParam, 0, 0);
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}
